Skip to content

Fix/security audit idor remediation#530

Merged
ginccc merged 5 commits into
mainfrom
fix/security-audit-idor-remediation
Jun 11, 2026
Merged

Fix/security audit idor remediation#530
ginccc merged 5 commits into
mainfrom
fix/security-audit-idor-remediation

Conversation

@ginccc

@ginccc ginccc commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

This pull request addresses multiple high- and medium-severity findings from a recent security audit, focusing on preventing Insecure Direct Object Reference (IDOR) vulnerabilities and strengthening ownership validation throughout the EDDI platform. Key changes include the introduction of a centralized OwnershipValidator component, comprehensive ownership checks across all conversation and user memory REST endpoints, annotation hardening for GDPR and A2A endpoints, and improved test coverage for all new security logic. Additionally, the codebase has been refactored to ensure a single source of truth for ownership checks and to reduce compliance risks in logging.

Security Audit Remediation and Ownership Validation

  • Centralized ownership validation:

    • Added a new OwnershipValidator component with methods for user access validation, admin bypass, and audit logging. All ownership checks are no-ops when authorization is disabled (dev mode).
    • All conversation and user memory REST endpoints now validate that the authenticated user is the owner of the resource, preventing IDOR attacks. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]
    • Group conversation endpoints enforce ownership checks and result filtering.
  • Interface and annotation hardening:

    • Moved @RolesAllowed("eddi-admin") from the implementation to the IRestGdprAdmin interface for more robust GDPR admin protection. [1] [2] [3] [4]
    • Added @PermitAll to all 5 A2A GET discovery endpoints to clarify intentional public access. [1] [2] [3] [4] [5] [6]

Codebase and API improvements

  • User memory deletion now performs ownership validation by looking up the entry before deletion, returning 404 if not found. This required adding findEntryById to the IUserMemoryStore interface and implementing it for both MongoDB and PostgreSQL backends. [1] [2] [3] [4]
  • Removed duplicate requireOwnerOrAdmin logic from McpToolUtils; now, all MCP memory tools use the centralized validator.

Test coverage and compliance

  • Added 36 new tests covering all security-critical ownership validation paths, updated existing tests to support new constructor parameters and lookup patterns, and ensured 100% pass rate.
  • Audit log hardening: WARN-level logs from ownership checks no longer include PII; full details are available at DEBUG level only.

These changes collectively close all critical and medium security findings, enforce strong resource ownership throughout the platform, and improve compliance and maintainability.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • ♻️ Refactoring (no functional changes)
  • 🔧 Chore (dependency updates, CI changes, etc.)

Checklist

  • My code follows the project's code style
  • I have added tests that prove my fix/feature works
  • Existing tests pass locally (./mvnw clean verify -DskipITs)
  • I have updated documentation if needed
  • My commit messages follow conventional commits
  • I have not committed any secrets, API keys, or tokens
  • This PR has a clear, focused scope (one concern per PR)

Summary by CodeRabbit

  • Security
    • Global ownership/authorization checks added for conversations, group conversations and user memories; admin role enforced for GDPR admin endpoints; A2A GET discovery marked public where appropriate.
  • Bug Fixes
    • Hardened fail-closed behavior, log-injection sanitization, ordering fixes (ownership checks), and 404-on-delete-after-lookup behavior.
  • Tests
    • New and extended tests covering ownership validation and security scenarios.
  • Documentation
    • Changelog updated with Security Audit Remediation entry.

ginccc added 4 commits June 10, 2026 14:26
…ions, memories, groups

- NEW: OwnershipValidator utility (validateUserAccess, validateAndResolveUserId, requireOwnerOrAdmin)
- RestAgentEngine: ownership checks on all conversation-scoped endpoints, userId resolution on startConversation
- RestUserMemoryStore: ownership checks on all userId-scoped endpoints
- RestGroupConversation: ownership checks on read/delete, userId validation on discuss, filtered list for non-admins
- IRestGdprAdmin: moved @RolesAllowed to interface level (was implementation-only)
- RestA2AEndpoint: added @permitAll to GET discovery endpoints (documents intentional public access)
- McpToolUtils: added requireOwnerOrAdmin() for MCP-level ownership validation
- McpMemoryTools: added ownership checks to all read tools (userId param validated against caller)

Admin role (eddi-admin) bypasses all ownership checks. All checks are no-ops when auth is disabled.
Updated 4 test files to pass SecurityIdentity and OwnershipValidator mocks
to the modified constructors of RestAgentEngine, RestUserMemoryStore, and
RestGroupConversation.
…nsolidation, deleteMemory ownership

- M1: Remove duplicate requireOwnerOrAdmin from McpToolUtils; McpMemoryTools injects OwnershipValidator directly

- M3: Sanitize PII in WARN logs; full details at DEBUG level only

- M4: Narrow catch in validateConversationOwnership to ResourceNotFoundException + ResourceStoreException

- BUG-2: Add findEntryById to IUserMemoryStore (Mongo + Postgres); deleteMemory validates ownership before deletion
…new tests)

- OwnershipValidatorTest [NEW]: 24 tests for all 4 public methods

- RestAgentEngineTest: 5 ownership validation tests + default descriptor stub

- RestUserMemoryStoreTest: 3 deleteMemory ownership tests

- RestGroupConversationTest: 4 ownership validation tests (IDOR, list filtering)

- McpMemoryToolsTest: updated constructor for OwnershipValidator injection
@ginccc ginccc requested a review from rolandpickl as a code owner June 10, 2026 13:19
@ginccc ginccc requested a review from Copilot June 10, 2026 13:19
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a centralized OwnershipValidator and enforces ownership checks across user memory stores, conversation and group conversation REST endpoints, and MCP memory tools; hardens GDPR and A2A endpoint annotations; updates constructors and tests; and documents the changes in the changelog.

Changes

Security Audit Remediation: IDOR Prevention & Ownership Validation

Layer / File(s) Summary
Core OwnershipValidator implementation
src/main/java/ai/labs/eddi/engine/security/OwnershipValidator.java, src/test/java/ai/labs/eddi/engine/security/OwnershipValidatorTest.java
Centralized authorization utility gated by authorization.enabled config (default false) providing isAuthEnabled, validateUserAccess, validateAndResolveUserId, and requireOwnerOrAdmin; nested tests cover enforcement, admin bypass, and no-op paths.
User memory store ownership validation
src/main/java/ai/labs/eddi/configs/properties/IUserMemoryStore.java, src/main/java/ai/labs/eddi/configs/properties/mongo/MongoUserMemoryStore.java, src/main/java/ai/labs/eddi/datastore/postgres/PostgresUserMemoryStore.java, src/main/java/ai/labs/eddi/configs/properties/rest/RestUserMemoryStore.java, src/test/java/ai/labs/eddi/configs/properties/rest/RestUserMemoryStoreTest.java
Adds findEntryById to interface; Mongo/Postgres implement lookup by id; RestUserMemoryStore injects SecurityIdentity and OwnershipValidator and validates access for list/search/get/upsert/delete/count; delete validates ownership after lookup; tests refactored to cover owner, not-found, and non-owner scenarios.
Agent conversation endpoint ownership validation
src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java, src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineTest.java
RestAgentEngine now injects IConversationDescriptorStore, SecurityIdentity, and OwnershipValidator; resolves userId for start via validateAndResolveUserId; adds validateConversationOwnership used before end/read/log/state/rerun/say/undo/redo operations with fail-closed behavior on store errors; tests added for impersonation and ownership cases.
Group conversation endpoint ownership validation
src/main/java/ai/labs/eddi/engine/internal/RestGroupConversation.java, src/test/java/ai/labs/eddi/engine/internal/RestGroupConversationTest.java, src/test/java/ai/labs/eddi/engine/internal/RestGroupConversationExtendedTest.java
RestGroupConversation injects SecurityIdentity and OwnershipValidator; discuss/discussStreaming resolve userId via validateAndResolveUserId (blank→anonymous); readGroupConversation and deleteGroupConversation enforce requireOwnerOrAdmin after loading the conversation; listGroupConversations filters to caller-owned for non-admins when auth enabled; tests updated.
MCP memory tools ownership validation
src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java, src/test/java/ai/labs/eddi/engine/mcp/McpMemoryToolsTest.java
McpMemoryTools now accepts an OwnershipValidator and calls validateUserAccess(identity, userId) for the MCP user-memory tools after role and userId presence checks; tests wire the new dependency.
GDPR and A2A security annotation hardening
src/main/java/ai/labs/eddi/engine/gdpr/IRestGdprAdmin.java, src/main/java/ai/labs/eddi/engine/gdpr/RestGdprAdmin.java, src/main/java/ai/labs/eddi/engine/a2a/RestA2AEndpoint.java
Moves @RolesAllowed("eddi-admin") to the GDPR interface; removes class-level role annotation from RestGdprAdmin; marks A2A GET endpoints with @PermitAll to explicitly allow public discovery endpoints while keeping JSON-RPC POST authenticated.
Security audit documentation
docs/changelog.md
Adds changelog entry (2026-06-10) documenting IDOR remediation, OwnershipValidator, endpoint integrations, follow-up hardening, and test coverage additions.

🎯 4 (Complex) | ⏱️ ~45 minutes

  • labsai/EDDI#485: Touches the same A2A capability discovery endpoints; related to adding and exposing those endpoints which are now annotated with @PermitAll.

Suggested reviewers

  • rolandpickl

🐰 Ownership guards now stand tall and true,
Each endpoint knows just what users can do;
Admins may bypass, the configs allow,
IDOR's prevented—security takes a bow!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main objective of the PR—remediating IDOR and ownership validation security findings—and is concise and clear.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/security-audit-idor-remediation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Comment thread src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java Fixed
Comment thread src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java Fixed
Comment thread src/main/java/ai/labs/eddi/engine/security/OwnershipValidator.java Fixed
Comment thread src/main/java/ai/labs/eddi/engine/security/OwnershipValidator.java Fixed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR remediates security-audit IDOR findings by centralizing resource ownership enforcement and applying it across conversation, group conversation, user memory REST endpoints, and MCP tools, while also hardening security annotations for GDPR admin and public A2A discovery endpoints.

Changes:

  • Introduces a centralized OwnershipValidator and wires it into REST + MCP entry points to enforce owner/admin access control.
  • Updates user-memory deletion to validate ownership via an ID lookup (findEntryById) before deleting and return 404 when the entry does not exist.
  • Hardens security annotations by moving GDPR admin @RolesAllowed to the interface and explicitly marking A2A discovery endpoints @PermitAll, with accompanying test updates/additions.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/main/java/ai/labs/eddi/engine/security/OwnershipValidator.java Adds centralized ownership validation (owner/admin checks, userId resolution) with audit logging.
src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java Enforces ownership checks for MCP memory read tools via OwnershipValidator.
src/main/java/ai/labs/eddi/engine/internal/RestGroupConversation.java Adds ownership validation on read/delete and filters list results for non-admin callers; resolves userId via validator.
src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java Adds conversation ownership validation by loading the conversation descriptor and enforcing owner/admin access; resolves start userId via validator.
src/main/java/ai/labs/eddi/engine/gdpr/RestGdprAdmin.java Removes implementation-level @RolesAllowed after moving it to the interface.
src/main/java/ai/labs/eddi/engine/gdpr/IRestGdprAdmin.java Adds interface-level @RolesAllowed("eddi-admin") for GDPR admin endpoints.
src/main/java/ai/labs/eddi/engine/a2a/RestA2AEndpoint.java Marks A2A discovery GET endpoints as intentionally public with @PermitAll.
src/main/java/ai/labs/eddi/datastore/postgres/PostgresUserMemoryStore.java Implements findEntryById for ownership validation prior to delete.
src/main/java/ai/labs/eddi/configs/properties/rest/RestUserMemoryStore.java Adds ownership validation to all endpoints and validates delete via entry lookup prior to deletion.
src/main/java/ai/labs/eddi/configs/properties/mongo/MongoUserMemoryStore.java Implements findEntryById for ownership validation prior to delete.
src/main/java/ai/labs/eddi/configs/properties/IUserMemoryStore.java Adds findEntryById to support ownership validation on delete.
src/test/java/ai/labs/eddi/engine/security/OwnershipValidatorTest.java New unit tests covering validator behavior (auth on/off, admin bypass, mismatch failures).
src/test/java/ai/labs/eddi/engine/mcp/McpMemoryToolsTest.java Updates test wiring for new OwnershipValidator dependency.
src/test/java/ai/labs/eddi/engine/internal/RestGroupConversationTest.java Adds ownership-validation tests and updates constructor dependencies.
src/test/java/ai/labs/eddi/engine/internal/RestGroupConversationExtendedTest.java Updates constructor dependencies to include identity + validator.
src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineTest.java Adds ownership-validation tests and updates constructor dependencies to include descriptor store + validator.
src/test/java/ai/labs/eddi/configs/properties/rest/RestUserMemoryStoreTest.java Updates delete-memory tests to cover 204/404/403 behavior with ownership validation.
docs/changelog.md Documents the security audit remediation, ownership validator, and test coverage changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java
Comment thread src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java
Comment thread src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java
Comment thread src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java
Comment thread src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/ai/labs/eddi/engine/mcp/McpMemoryToolsTest.java (1)

27-35: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add ownership validation integration tests for MCP memory tools.

The test setup now includes OwnershipValidator, but no tests verify the ownership validation integration. For a security feature preventing IDOR attacks, explicit test coverage is essential.

Consider adding tests for:

  • validateUserAccess is called with correct identity and userId parameters
  • Non-owner receives ForbiddenException when attempting to access another user's memories
  • Admin bypass behavior (if applicable to MCP tools)
  • Owner can successfully access their own memories after ownership check

Similar ownership tests were added to RestGroupConversationTest (lines 226-299 in that file) and can serve as a pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/ai/labs/eddi/engine/mcp/McpMemoryToolsTest.java` around lines
27 - 35, Add unit tests that exercise McpMemoryTools' ownership integration: in
the test class (setUp uses mocked OwnershipValidator and SecurityIdentity) add
tests that 1) verify OwnershipValidator.validateUserAccess is invoked with the
SecurityIdentity mock and the target userId when calling the MCP methods that
load user memories, using Mockito.verify; 2) stub validateUserAccess to throw a
ForbiddenException and assert the MCP method propagates/throws
ForbiddenException for non-owners; 3) stub validateUserAccess to allow access
and assert owners can read their memories; and 4) if admin bypass behavior
exists, stub SecurityIdentity as admin and assert validateUserAccess is not
blocking (or verify bypass path). Target methods/classes: McpMemoryTools and
OwnershipValidator.validateUserAccess; use the existing userMemoryStore and
jsonSerialization mocks to drive responses and Mockito.verify/when to assert
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/changelog.md`:
- Around line 32-35: Update the changelog to remove ambiguity by clarifying that
the addition of requireOwnerOrAdmin() in McpToolUtils was the initial fix and
was later replaced by direct use of OwnershipValidator, or alternatively change
the earlier entry to reflect the final approach: locate the entries referencing
McpToolUtils and requireOwnerOrAdmin and the later entry mentioning
OwnershipValidator and either annotate the first as "initial approach" or
rewrite it to state the final solution (direct OwnershipValidator usage) so the
document shows a single, unambiguous final security posture.

In `@src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java`:
- Around line 248-267: The current validateConversationOwnership method silently
skips ownership checks when conversationDescriptorStore.readDescriptor(...)
throws ResourceStoreException, creating a fail-open; change the
ResourceStoreException handling so the method fails-closed by throwing a
ForbiddenException (or otherwise denying access) instead of just logging; ensure
the thrown ForbiddenException references that ownership could not be verified
and includes the original ResourceStoreException as the cause so callers see the
underlying error; keep the existing handling for ResourceNotFoundException if
you want the actual operation to handle missing descriptors.

In `@src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java`:
- Around line 62-67: In listUserMemories, getVisibleMemories,
searchUserMemories, getMemoryByKey, and countUserMemories, move the parameter
null/blank checks for userId (and any other required params like key) to occur
immediately after requireRole(...) and before calling
ownershipValidator.validateUserAccess(identity, userId); specifically, validate
userId (and key where applicable) and return errorJson("userId is required") or
appropriate errorJson before invoking ownershipValidator.validateUserAccess in
the methods listUserMemories, getVisibleMemories, searchUserMemories,
getMemoryByKey, and countUserMemories so the ownership validator never receives
null/blank values.

---

Outside diff comments:
In `@src/test/java/ai/labs/eddi/engine/mcp/McpMemoryToolsTest.java`:
- Around line 27-35: Add unit tests that exercise McpMemoryTools' ownership
integration: in the test class (setUp uses mocked OwnershipValidator and
SecurityIdentity) add tests that 1) verify OwnershipValidator.validateUserAccess
is invoked with the SecurityIdentity mock and the target userId when calling the
MCP methods that load user memories, using Mockito.verify; 2) stub
validateUserAccess to throw a ForbiddenException and assert the MCP method
propagates/throws ForbiddenException for non-owners; 3) stub validateUserAccess
to allow access and assert owners can read their memories; and 4) if admin
bypass behavior exists, stub SecurityIdentity as admin and assert
validateUserAccess is not blocking (or verify bypass path). Target
methods/classes: McpMemoryTools and OwnershipValidator.validateUserAccess; use
the existing userMemoryStore and jsonSerialization mocks to drive responses and
Mockito.verify/when to assert behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5e024c6-b7b2-4f64-8001-36ff7f9fb5c2

📥 Commits

Reviewing files that changed from the base of the PR and between e35c57e and 320f6ce.

📒 Files selected for processing (18)
  • docs/changelog.md
  • src/main/java/ai/labs/eddi/configs/properties/IUserMemoryStore.java
  • src/main/java/ai/labs/eddi/configs/properties/mongo/MongoUserMemoryStore.java
  • src/main/java/ai/labs/eddi/configs/properties/rest/RestUserMemoryStore.java
  • src/main/java/ai/labs/eddi/datastore/postgres/PostgresUserMemoryStore.java
  • src/main/java/ai/labs/eddi/engine/a2a/RestA2AEndpoint.java
  • src/main/java/ai/labs/eddi/engine/gdpr/IRestGdprAdmin.java
  • src/main/java/ai/labs/eddi/engine/gdpr/RestGdprAdmin.java
  • src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java
  • src/main/java/ai/labs/eddi/engine/internal/RestGroupConversation.java
  • src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java
  • src/main/java/ai/labs/eddi/engine/security/OwnershipValidator.java
  • src/test/java/ai/labs/eddi/configs/properties/rest/RestUserMemoryStoreTest.java
  • src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineTest.java
  • src/test/java/ai/labs/eddi/engine/internal/RestGroupConversationExtendedTest.java
  • src/test/java/ai/labs/eddi/engine/internal/RestGroupConversationTest.java
  • src/test/java/ai/labs/eddi/engine/mcp/McpMemoryToolsTest.java
  • src/test/java/ai/labs/eddi/engine/security/OwnershipValidatorTest.java
💤 Files with no reviewable changes (1)
  • src/main/java/ai/labs/eddi/engine/gdpr/RestGdprAdmin.java

Comment thread docs/changelog.md
Comment thread src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java
Comment thread src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java
…nd MCP validation order

- RestAgentEngine: sanitize conversationId in debug logs via LogSanitizer
- OwnershipValidator: sanitize all user-provided values in debug logs
- RestAgentEngine: fail-closed on ResourceStoreException in ownership check
  (previously silently skipped, allowing unauthorized access during DB errors)
- McpMemoryTools: reorder validation — null/blank check before ownership
  check in all 5 read-only tools (prevents ForbiddenException on missing input)
- Changelog: clarify MCP ownership fix chronology, add remediation entry

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java (1)

139-146: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Arm the AsyncResponse timeout before the ownership lookup.

Lines 143 and 163 call validateConversationOwnership(conversationId) before Lines 171-172 register the timeout handler. Since that helper does a synchronous conversationDescriptorStore.readDescriptor(...), a slow or hung descriptor lookup can keep say/rerun outside the agentTimeout window and block the request thread before the async timeout is even active.

♻️ Suggested change
+    private void configureAsyncTimeout(AsyncResponse response) {
+        response.setTimeout(agentTimeout, TimeUnit.SECONDS);
+        response.setTimeoutHandler(asyncResp -> asyncResp.resume(Response.status(Response.Status.REQUEST_TIMEOUT).build()));
+    }
+
     `@Override`
     public void rerunLastConversationStep(String conversationId, String language, Boolean returnDetailed, Boolean returnCurrentStepOnly,
                                           List<String> returningFields, final AsyncResponse response) {
         checkNotNull(conversationId, "conversationId");
         checkNotEmpty(language, "language");
+        configureAsyncTimeout(response);
         validateConversationOwnership(conversationId);
 
         sayInternal(conversationId, returnDetailed, returnCurrentStepOnly, returningFields,
                 new InputData("", Map.of(KEY_LANG, new Context(string, language))), true, response);
     }
@@
     public void sayWithinContext(final String conversationId, final Boolean returnDetailed, final Boolean returnCurrentStepOnly,
                                  final List<String> returningFields, final InputData inputData, final AsyncResponse response) {
 
         checkNotNull(conversationId, "conversationId");
         checkNotNull(inputData, "inputData");
         checkNotNull(inputData.getInput(), "inputData.input");
+        configureAsyncTimeout(response);
         validateConversationOwnership(conversationId);
 
         sayInternal(conversationId, returnDetailed, returnCurrentStepOnly, returningFields, inputData, false, response);
     }
@@
     private void sayInternal(String conversationId, Boolean returnDetailed, Boolean returnCurrentStepOnly, List<String> returningFields,
                              InputData inputData, boolean rerunOnly, AsyncResponse response) {
-
-        response.setTimeout(agentTimeout, TimeUnit.SECONDS);
-        response.setTimeoutHandler(asyncResp -> asyncResp.resume(Response.status(Response.Status.REQUEST_TIMEOUT).build()));
-
         try {
             conversationService.say(conversationId, returnDetailed, returnCurrentStepOnly, returningFields, inputData, rerunOnly, response::resume);

As per coding guidelines, "Code must be thread-safe and non-blocking. REST endpoints use JAX-RS AsyncResponse. Tasks execute synchronously but must not block for extended periods."

Also applies to: 160-173

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java` around lines
139 - 146, The AsyncResponse timeout must be armed before any synchronous
blocking calls like validateConversationOwnership(conversationId); modify
rerunLastConversationStep (and the other affected endpoint method) so that you
register the AsyncResponse timeout/timeout handler (the code that sets the agent
timeout on the provided AsyncResponse) immediately upon entry, then perform
validateConversationOwnership(conversationId) and the subsequent
sayInternal(...) call; ensure sayInternal(...) and
validateConversationOwnership(...) remain unchanged but are executed after the
timeout is registered so a hung conversationDescriptorStore.readDescriptor(...)
cannot prevent the JAX-RS timeout from firing.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java`:
- Around line 64-67: In McpMemoryTools, the current requireRole(identity,
authEnabled, "eddi-viewer") calls block callers who only have eddi-admin; update
those viewer-gated checks to allow either eddi-viewer or eddi-admin before
calling ownershipValidator.validateUserAccess(...). Concretely, in the
McpMemoryTools methods containing the requireRole calls (the occurrences around
the existing requireRole(...) at lines shown), replace the single-role gate with
a check that succeeds if identity.hasRole("eddi-viewer") OR
identity.hasRole("eddi-admin") (or use an existing helper that accepts multiple
roles), then proceed to ownershipValidator.validateUserAccess(identity, userId)
as before. Ensure the change is applied to every occurrence of requireRole in
this class so eddi-admin can bypass the viewer check consistently.

---

Outside diff comments:
In `@src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java`:
- Around line 139-146: The AsyncResponse timeout must be armed before any
synchronous blocking calls like validateConversationOwnership(conversationId);
modify rerunLastConversationStep (and the other affected endpoint method) so
that you register the AsyncResponse timeout/timeout handler (the code that sets
the agent timeout on the provided AsyncResponse) immediately upon entry, then
perform validateConversationOwnership(conversationId) and the subsequent
sayInternal(...) call; ensure sayInternal(...) and
validateConversationOwnership(...) remain unchanged but are executed after the
timeout is registered so a hung conversationDescriptorStore.readDescriptor(...)
cannot prevent the JAX-RS timeout from firing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c24dae9-2bf6-4f76-bdac-514fd24c9afa

📥 Commits

Reviewing files that changed from the base of the PR and between 320f6ce and 3b348fe.

📒 Files selected for processing (4)
  • docs/changelog.md
  • src/main/java/ai/labs/eddi/engine/internal/RestAgentEngine.java
  • src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java
  • src/main/java/ai/labs/eddi/engine/security/OwnershipValidator.java
✅ Files skipped from review due to trivial changes (1)
  • docs/changelog.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/ai/labs/eddi/engine/security/OwnershipValidator.java

Comment on lines 64 to +67
requireRole(identity, authEnabled, "eddi-viewer");
if (userId == null || userId.isBlank())
return errorJson("userId is required");
ownershipValidator.validateUserAccess(identity, userId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== McpToolUtils.requireRole implementation =="
fd 'McpToolUtils.java' . -x sh -c 'echo "--- $1 ---"; sed -n "1,240p" "$1"' sh {}

echo
echo "== Viewer/admin role references around MCP tools =="
rg -n -C2 'requireRole\s*\(|eddi-viewer|eddi-admin' .

Repository: labsai/EDDI

Length of output: 50367


Fix MCP viewer-gated role checks to allow intended eddi-admin access
McpToolUtils.requireRole(...) only passes when identity.hasRole(role) (no role hierarchy), so the viewer-gated MCP methods in src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java (lines 64-67, 94-97, 122-125, 146-149, 234-237) will reject callers with only eddi-admin before ownershipValidator.validateUserAccess(...) can run. Since OwnershipValidator does allow an eddi-admin bypass for ownership checks, these gates are inconsistent—either grant eddi-admin the eddi-viewer role (in the IdP) or change those methods to also accept eddi-admin.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/ai/labs/eddi/engine/mcp/McpMemoryTools.java` around lines 64 -
67, In McpMemoryTools, the current requireRole(identity, authEnabled,
"eddi-viewer") calls block callers who only have eddi-admin; update those
viewer-gated checks to allow either eddi-viewer or eddi-admin before calling
ownershipValidator.validateUserAccess(...). Concretely, in the McpMemoryTools
methods containing the requireRole calls (the occurrences around the existing
requireRole(...) at lines shown), replace the single-role gate with a check that
succeeds if identity.hasRole("eddi-viewer") OR identity.hasRole("eddi-admin")
(or use an existing helper that accepts multiple roles), then proceed to
ownershipValidator.validateUserAccess(identity, userId) as before. Ensure the
change is applied to every occurrence of requireRole in this class so eddi-admin
can bypass the viewer check consistently.

@ginccc ginccc merged commit cd7b7ba into main Jun 11, 2026
23 checks passed
@ginccc ginccc deleted the fix/security-audit-idor-remediation branch June 11, 2026 09:54
@coderabbitai coderabbitai Bot mentioned this pull request Jun 25, 2026
13 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants